#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <map>
#include "fifo.h"

#define wait() asm volatile("" : : : "memory");

void check(bool test) {
	if (!test)
		exit(1);
}

void *thread1(void*);
void *thread2(void*);
void *thread3(void*);
void *thread4(void*);

nbds::fifo<int> ds;

std::map<int, int> reader1;
std::map<int, int> reader2;
int reader1c = 0, reader2c = 0;
volatile bool read = true;

int main() {
	pthread_t t1, t2, t3, t4;
	
	pthread_create(&t1, NULL, thread1, NULL);
	pthread_create(&t2, NULL, thread2, NULL);
	pthread_create(&t3, NULL, thread3, NULL);
	pthread_create(&t4, NULL, thread4, NULL);

	pthread_join(t2, NULL);
	pthread_join(t4, NULL);
	read = false;
	pthread_join(t1, NULL);
	pthread_join(t3, NULL);

	check(reader1c + reader2c == 50000);
	check(reader1[1] == 1 || reader2[1] == 1);
	check(reader1[2] == 2 || reader2[2] == 2);
	check(reader1[3] == 3 || reader2[3] == 3);
	check(reader1[4] == 4 || reader2[4] == 4);
	check(reader1[5] == 5 || reader2[5] == 5);
	check(reader1[6] == 6 || reader2[6] == 6);
	check(reader1[7] == 7 || reader2[7] == 7);
	check(reader1[8] == 8 || reader2[8] == 8);
	check(reader1[9] == 9 || reader2[9] == 9);
	check(reader1[10] == 10 || reader2[10] == 10);

	exit(0);
}

void *thread1(void*) { 
	while (read) {
		int x = ds.dequeue();

		if (x) {
			reader1[x] = x;
			reader1c++;
		}
	}
}

void *thread2(void*) {
	for (int i = 0; i < 5000; i++) {
		ds.enqueue(1);
		for (volatile int i = 0; i < 100; i++) wait();
		ds.enqueue(2);
		for (volatile int i = 0; i < 1000; i++) wait();
		ds.enqueue(3);
		for (volatile int i = 0; i < 100000; i++) wait();
		ds.enqueue(4);
		for (volatile int i = 0; i < 1000; i++) wait();
		ds.enqueue(5);
	}
}

void *thread3(void*) {
	while (read) {
		int x = ds.dequeue();

		if (x) {
			reader2[x] = x;
			reader2c++;
		}
	}
}

void *thread4(void*) {
	for (int i = 0; i < 5000; i++) {
		ds.enqueue(6);
		for (volatile int i = 0; i < 10; i++) wait();
		ds.enqueue(7);
		for (volatile int i = 0; i < 100000; i++) wait();
		ds.enqueue(8);
		for (volatile int i = 0; i < 100; i++) wait();
		ds.enqueue(9);
		ds.enqueue(10);
	}
}

